package reversepolish;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Stack;

/**
 * @description:
 * @author: 陈瑞东
 * @create: 2021-06-25 09:41
 */
public class ReversePolish {

    public static void main(String[] args) {
        ReversePolish reversePolish = new ReversePolish();


        JFrame jFrame = new JFrame("逆波兰表达式计算器");
        Font titleFont = new Font("StSong", Font.BOLD, 40);
        Font resultFont = new Font("StSong", Font.BOLD, 30);
        Font normalFont = new Font("StSong", Font.BOLD, 24);
        Font buttonFont = new Font("StSong", Font.BOLD, 30);

        UIManager.put("Label.font", normalFont);
        UIManager.put("Button.font", buttonFont);
        UIManager.put("TextField.font", resultFont);

        Box hBox = Box.createHorizontalBox();
        Box vBox = Box.createVerticalBox();

        hBox.add(Box.createHorizontalStrut(20));
        hBox.add(vBox);
        hBox.add(Box.createHorizontalStrut(20));

        Box labelBox = Box.createHorizontalBox();
        JLabel calcLabel = new JLabel("逆波兰表达式计算器");
        calcLabel.setFont(titleFont);
        labelBox.add(calcLabel);

        Box infixBox= Box.createHorizontalBox();
        JLabel infixLabel = new JLabel("中缀表达式：");
        JTextField infixField = new JTextField(20);
        infixBox.add(infixLabel);
        infixBox.add(Box.createHorizontalStrut(25));
        infixBox.add(infixField);

        Box polandBox= Box.createHorizontalBox();
        JLabel polandLabel = new JLabel("逆波兰表达式：");
        JTextField polandField = new JTextField(20);
        polandBox.add(polandLabel);
        polandBox.add(polandField);

        Box resultBox = Box.createHorizontalBox();
        JLabel resultTipJLabel = new JLabel("结果：");
        JLabel resultJLabel = new JLabel("");
        resultTipJLabel.setFont(resultFont);
        resultJLabel.setFont(resultFont);
        resultBox.add(resultTipJLabel);
        resultBox.add(resultJLabel);

        Box buttonBox = Box.createHorizontalBox();
        JButton generate = new JButton("生成");
        JButton convert = new JButton("转换");
        JButton calculation = new JButton("计算");
        buttonBox.add(generate);
        buttonBox.add(Box.createHorizontalStrut(20));
        buttonBox.add(convert);
        buttonBox.add(Box.createHorizontalStrut(20));
        buttonBox.add(calculation);

        generate.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String expression = reversePolish.createInfixExpression(6);
                infixField.setText(expression);
            }
        });

        convert.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String text = infixField.getText();
                if("".equals(text)){
                    JOptionPane.showMessageDialog(jFrame, "请输入中缀表达式！", "提示", JOptionPane.WARNING_MESSAGE);
                }
                String polandExpression = reversePolish.infixToPoland(text);
                polandField.setText(polandExpression);
            }
        });

        calculation.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String text = polandField.getText();
                if ("".equals(text)) {
                    JOptionPane.showMessageDialog(jFrame, "请输入逆波兰表达式！", "提示", JOptionPane.WARNING_MESSAGE);
                }
                double result = reversePolish.calculationPoland(text);
                resultJLabel.setText(String.valueOf(Math.round(result * 100) / 100f));
            }
        });

        vBox.add(Box.createVerticalStrut(15));
        vBox.add(labelBox);
        vBox.add(Box.createVerticalStrut(15));
        vBox.add(resultBox);
        vBox.add(Box.createVerticalStrut(15));
        vBox.add(infixBox);
        vBox.add(Box.createVerticalStrut(15));
        vBox.add(polandBox);
        vBox.add(Box.createVerticalStrut(40));
        vBox.add(buttonBox);
        vBox.add(Box.createVerticalStrut(50));

        jFrame.add(hBox);

        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jFrame.pack();
        jFrame.setResizable(false);
        jFrame.setLocationRelativeTo(null);
        jFrame.setVisible(true);

//        ReversePolish reversePolish = new ReversePolish();
//        for (int i = 0; i < 10; i++) {
//            String expression = reversePolish.createInfixExpression(4);
//            String polandExpression = reversePolish.infixToPoland(expression);
//            double result = reversePolish.calculationPoland(polandExpression);
//            System.out.println(expression + " --> " + polandExpression + " = " + Math.round(result * 100) / 100f);
//        }
    }


    /**
     * 计算逆波兰表达式
     * 规则：
     *      遇到数字，压入数字栈
     *      遇到符号，从数字栈中取出两个数进行计算，将计算结果重新压入数字栈
     *      最后栈中剩余一个数字，就是最终结果
     * @param expression 逆波兰表达式
     * @return 计算结果
     */
    public double calculationPoland(String expression){
        // 7 1 4 / + 5 +
        // 创建数字栈
        Stack<String> numStack = new Stack<>();
        // 将表达式转成List
        List<String> expressionList = expressionToList(expression);
        // 开始扫描表达式
        for (String str : expressionList) {
            // 如果遇到数字，直接入栈
            if(str.matches("[\\d\\.]+")){
                numStack.push(str);
                continue;
            }
            // 如果遇到符号，则从数字栈中弹出两个元素进行计算
            if("+-*/".contains(str)){
                double num1 = Double.parseDouble(numStack.pop());
                double num2 = Double.parseDouble(numStack.pop());
                // 执行计算，并入数字栈
                numStack.push(String.valueOf(operationCalcation(num1, num2, str)));
            }
        }

        return Double.parseDouble(numStack.pop());
    }

    /**
     * 执行两数之间的基本运算
     * @param num1 数字1
     * @param num2 数字2
     * @return 计算结果
     */
    public double operationCalcation(double num1, double num2, String operator){
        double result = 0;
        switch (operator){
            case "+":
                result = num1 + num2;
                break;
            case "-":
                result = num2 - num1;
                break;
            case "*":
                result = num1 * num2;
                break;
            case "/":
                result = num2 / num1;
                break;
            default:
                throw new RuntimeException("传入了错误的运算符");
        }
        return result;
    }

    /**
     * 中缀表达式转逆波兰表达式
     * 规则：
     *      从左往右扫描表达式
     *      遇到数字，直接拼接
     *      遇到运算符，如果符号栈中没有数据，则入栈，
     *                如果有数据，则弹出数据比较符号优先级，
     *                      若该符号优先级较大，则入栈，
     *                      若较小或相等，则将弹出符号拼接，继续弹出栈顶符号进行比较
     *      遇到左括号，先入栈
     *      遇到右括号，将符号从栈中弹出进行拼接，直到弹出左括号
     * @param expression 中缀表达式字符串
     * @return 逆波兰表达式字符串
     */
    public String infixToPoland(String expression){
        // 用于拼接逆波兰表达式
        StringBuilder polandExpression = new StringBuilder();
        // 将表达式转为List形式
        List<String> expressionList = expressionToList(expression);
        // 创建一个符号栈
        Stack<String> operatorStack = new Stack<>();
        // 遍历表达式List
        for (int i = 0; i < expressionList.size(); i++) {
            String str = expressionList.get(i);
            // 如果是数字
            if(str.matches("[\\d\\.]+")){
                // 直接拼接
                polandExpression.append(str);
                // 判断接下来一个字符是否是数字，将接下来的数字全部拼接
                while(i != expressionList.size() - 1 && (str = expressionList.get(i + 1)).matches("[\\d\\.]+")){
                    polandExpression.append(str);
                    // 将 i + 1
                    i++;
                }
                // 拼接空格
                polandExpression.append(" ");
                continue;
            }
            // 如果是符号
            if("+-*/".contains(str)){
                // 判断符号栈是否为空
                if(operatorStack.isEmpty()){
                    // 直接入栈
                    operatorStack.push(str);
                }else{
                    // 弹出栈顶符号比较优先级，若小于栈顶元素，则将栈顶元素拼接到表达式上
                    while(!operatorStack.isEmpty() && compareOperatorPriority(str, operatorStack.peek()) <= 0){
                        polandExpression.append(operatorStack.pop() + " ");
                    }
                    // 将运算符入栈
                    operatorStack.push(str);
                }
                continue;
            }
            // 如果是左括号，直接入栈
            if("(".equals(str)){
                operatorStack.push(str);
            }
            // 如果是右括号，则弹出左括号之前的所有运算符进行拼接
            if (")".equals(str)) {
                while(!operatorStack.isEmpty() && !"(".equals(str = operatorStack.pop())){
                    polandExpression.append(str + " ");
                }
            }
        }
        // 将符号栈中剩余符号进行拼接
        while(!operatorStack.isEmpty()){
            polandExpression.append(operatorStack.pop() + " ");
        }
        return polandExpression.toString();
    }

    /**
     * 获取运算符优先级
     * 约定：
     *      + - : 1
     *      * / : 2
     *      ( ) : 3
     * @param operator
     * @return
     */
    public int getOperatorPriority(String operator){
        int priority = -1;
        switch (operator){
            case "+":
            case "-":
                priority = 1;
                break;
            case "*":
            case "/":
                priority = 2;
                break;
        }
        return priority;
    }

    /**
     * 比较运算符优先级
     * @param o1 运算符1
     * @param o2 运算符2
     * @return
     *      1 : o1 >  o2
     *      0 : o1 == o2
     *     -1 : o1 <  o2
     */
    public int compareOperatorPriority(String o1, String o2){
        return getOperatorPriority(o1) - getOperatorPriority(o2);
    }


    /**
     * 将表达式字符串形式转为List形式
     * @param expression 表达式字符串
     * @return 表达式List
     */
    public List<String> expressionToList(String expression){
        List<String> expressionList = new ArrayList<>();
        // 从左往右扫描表达式
        for (int i = 0; i < expression.length(); i++) {
            String str = String.valueOf(expression.charAt(i));
            // 如果是运算符，则直接添加
            if("+-*/()".contains(str)){
                expressionList.add(str);
                continue;
            }
            // 如果是数字，则将之后连接的数字一并添加
            if(str.matches("[\\d]+")){
                StringBuilder builder = new StringBuilder(str);
                while(i < expression.length() - 1 && (str = String.valueOf(expression.charAt(i + 1))).matches("[\\d\\.]+")){
                    i++;
                    builder.append(str);
                }
                expressionList.add(builder.toString());
            }
        }
        return expressionList;
    }

    /**
     * 随机生成中缀表达式
     *
     * @param numCount 数字的数量
     * @return 中缀表达式字符串
     */
    public String createInfixExpression(int numCount) {
        // 生成 numCount 个随机数存入数据栈中
        Stack<Integer> numStack = new Stack<>();
        Random random = new Random();
        for (int i = 0; i < numCount; i++) {
            numStack.push(random.nextInt(20) + 1);
        }


        // 表达式字符串
        StringBuilder expression = new StringBuilder();
        // 记录还需生成的右括号数量
        int rightBracketsCountNeed = 0;
        // 是否可以生成小数点
        boolean canPoint = true;
        // 开始循环拼接表达式，最后一个数后不需要拼接除右括号之外的符号，因此不加入循环，单独处理
        while (numStack.size() > 2) {
            // 随机生成符号
            String operator = randomOperator(rightBracketsCountNeed != 0, canPoint);
            // 判断是否是左括号
            while("(".equals(operator)){
                // 右括号需求数量 +1
                rightBracketsCountNeed++;
                // 先拼接左括号
                expression.append(operator);
                // 再次生成运算符（不包括右括号），如果还是左括号，则继续循环
                operator = randomOperator(false, false);
            }
            // 拼接数字
            expression.append(numStack.pop());

            // 判断运算符是否是右括号
            while(")".equals(operator)){
                // 右括号需求数量 -1
                rightBracketsCountNeed--;
                // 拼接右括号
                expression.append(operator);
                // 再次生成运算符，如果还是右括号，则继续循环
                operator = randomOperator(rightBracketsCountNeed != 0, false);
            }
            // 拼接运算符
            expression.append(operator);
            // 如果是小数点，则直接拼接
            if(".".equals(operator)){
                canPoint = false;
            }else{
                canPoint = true;
            }
        }
        // 拼接最后一个数
        expression.append(numStack.pop());
        // 补齐右括号
        for (int i = 0; i < rightBracketsCountNeed; i++) {
            expression.append(")");
        }
        return expression.toString();
    }

    /**
     * 随机获取运算符
     *
     * @param hasRightBrackets 是否加入右括号
     * @param hasRightBrackets 是否加入小数点
     * @return 运算符
     */
    public String randomOperator(boolean hasRightBrackets, boolean hasPoint) {

        List<String> list = new ArrayList<>();
        Random random = new Random();

        for (int i = 0; i < 16; i++) {
            list.add("+");
        }
        for (int i = 0; i < 15; i++) {
            list.add("-");
        }
        for (int i = 0; i < 8; i++) {
            list.add("*");
        }
        for (int i = 0; i < 8; i++) {
            list.add("/");
        }
        if(hasPoint){
            for (int i = 0; i < 4; i++) {
                list.add(".");
            }
        }
        for (int i = 0; i < 1; i++) {
            list.add("(");
        }
        if(hasRightBrackets){
            for (int i = 0; i < 50; i++) {
                list.add(")");
            }
        }

        int index = random.nextInt(list.size() - 1) + 1;

        return list.get(index);
    }
}
